﻿using Modbus.Net8.Commom;
using System.IO.Ports;

namespace Modbus.Net8.Modbus
{
    /// <summary>
    /// Modbus串行通讯
    /// 串口通讯：https://blog.csdn.net/qq_45728381/article/details/143162808
    /// </summary>
    public class ModbusSerialComm
    {
        public SerialPort SerialPort { get; set; }

        public bool IsOpen
        {
            get
            {
                if (SerialPort == null)
                    return false; return SerialPort.IsOpen;
            }
        }

        /// <summary>
        /// Modbus RTU是一种紧凑的，采用二进制表示数据的方式；
        /// Modbus ASCII是一种人类可读的，冗长的表示方式。
        /// </summary>
        public bool IsRTU { get; set; } = true;

        /// <summary>
        /// 
        /// </summary>
        public void Init()
        {

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="portName">设置串口号</param>
        /// <param name="baudRate">设置波特率</param>
        /// <param name="dataBits">设置数据位</param>
        /// <param name="stopBits">设置停止位</param>
        /// <param name="parity">设置奇偶校验位</param>
        /// <returns></returns>
        public bool OpenComm(string portName, int baudRate = 9600, int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None)
        {
            SerialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
            SerialPort.DataReceived += SerialPort_DataReceived;     //异步读取数据
            SerialPort.Open();
            return true;
        }

        /// <summary>
        /// 异步读取数据触发事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                SerialPort sp = (SerialPort)sender;
                string receivedData = sp.ReadExisting();
                Console.WriteLine($"异步收到: {receivedData}");
            }
            catch (Exception ex)
            { }
        }

        public bool SendData(string data)
        {
            if (SerialPort != null && SerialPort.IsOpen)
            {
                SerialPort.Write(data);
            }
            return false;
        }

        public void Close()
        {
            if (SerialPort != null)
                SerialPort.Close();
        }
    }

    /// <summary>
    /// Modbus帮助类
    /// </summary>
    public class ModbusData
    {
        #region 属性

        /// <summary>
        /// 地址范围0-247(十进制)
        /// </summary>
        public byte Address { get; set; } = 0;
        /// <summary>
        /// 有效码范围1-225(十进制)
        /// </summary>
        public byte Function { get; set; }
        /// <summary>
        /// 相应报文的数据长度
        /// </summary>
        public int DataLen { get; set; } = 0;

        public byte[] Data { get; set; }

        #region TCP属性

        /// <summary>
        /// 事务处理标识符 2byte
        /// </summary>
        public ushort MbapTransId { get; set; }
        /// <summary>
        /// 单元标识符 1byte
        /// </summary>
        public byte MbapUnitId { get; set; }

        #endregion

        #endregion

        #region RTU

        #region 引用
        /// ModbusRTU协议
        /// https://blog.csdn.net/u011041241/article/details/109240555
        #endregion

        /// <summary>
        /// 获取Modbus数据报文（RTU方式）
        /// </summary>
        /// <param name="addr">地址范围0-247(十进制)</param>
        /// <param name="func">有效码范围1-225(十进制)</param>
        /// <param name="data"></param>
        /// <returns></returns>
        public static byte[] PackDataRtu(byte addr, byte func, byte[] data)
        {
            List<byte> bytes = new List<byte>();
            bytes.Add(addr);
            bytes.Add(func);
            bytes.AddRange(data);
            ushort crc = Crc16.ComputeCheckSum(bytes.ToArray());
            byte highByte = (byte)(crc >> 8);    // 获取高字节
            byte lowByte = (byte)(crc & 0xFF);   // 获取低字节
            bytes.Add(lowByte);
            bytes.Add(highByte);
            return bytes.ToArray();
        }
        //byte[] data = RTU.GetSendBytes(0x17, 3, new byte[] { 0, 4, 0, 4 });

        /// <summary>
        /// 解析Modbus报文（RTU方式）
        /// </summary>
        /// <param name="data"></param>
        /// <param name="hasCount"></param>
        /// <param name="rtu"></param>
        /// <returns></returns>
        public static bool ParseDataRtu(byte[] data, bool hasCount, out ModbusData rtu)
        {
            int idx = 0;
            rtu = new ModbusData();
            rtu.Address = data[idx++];      //0
            rtu.Function = data[idx++];     //1
            if (hasCount)
            {
                rtu.DataLen = data[idx++];      //2
                if (data.Length < rtu.DataLen + idx + 2)    //Data字节长度 + Data之前的字节数 + 校验值字节数
                    return false;
            }
            else
                rtu.DataLen = data.Length - idx - 2;

            ushort crc = Crc16.ComputeCheckSum(data, rtu.DataLen + idx);
            if (data[data.Length - 1] * 256 + data[data.Length - 2] != crc)
                return false;

            rtu.Data = new byte[rtu.DataLen];
            Buffer.BlockCopy(data, idx, rtu.Data, 0, rtu.DataLen);
            return true;
        }
        //RTU rtu;
        //RTU.ParesRTUData(new byte[] { 0x17, 3, 8, 0, 0, 0, 0x39, 0x41, 0x25, 0x24, 0xE1, 0x9d, 0x25 }, true, out rtu);

        #endregion

        #region ASCII

        #region 引用
        /// ModbusASCII协议
        /// https://www.modbus.cn/27886.html
        /// https://blog.csdn.net/weixin_43866583/article/details/132941539
        /// 报文的每个字节数据以两个ASCII字符进行发送。
        /// 例:有一个数据字节为 0X5B，它会被编码为两个字符 : 0x35 和 0x42 (ASCII 编码 0x35 ="5"， 0x42 ="B" )。
        /// 
        /// LRC校验可能不对，找不到标准答案
        #endregion

        /// <summary>
        /// 获取Modbus数据报文（RTU方式）
        /// 以:开头，以回车换行结尾
        /// </summary>
        /// <param name="addr">地址范围0-247(十进制)</param>
        /// <param name="func">有效码范围1-225(十进制)</param>
        /// <param name="data"></param>
        /// <returns></returns>
        public static byte[] PackDataAscii(byte addr, byte func, byte[] data)
        {
            List<byte> bytes = new List<byte> { 0x3a };
            bytes.SetByteAscii(addr);
            bytes.SetByteAscii(func);

            string strData = BitConverter.ToString(data).Replace("-", "");
            foreach (char s in strData)
                bytes.Add((byte)s);

            byte crc = LRC.ComputeCheckSum(bytes.ToArray());
            bytes.SetByteAscii(crc);

            bytes.Add(0x0d);
            bytes.Add(0x0a);
            //bytes.Insert(0, 0x3a);
            return bytes.ToArray();
        }
        //byte[] data = RTU.GetSendBytes(0x17, 3, new byte[] { 0, 4, 0, 4 });

        public static byte[] PackDataAscii(byte addr, byte func, string data)
        {
            List<byte> bytes = new List<byte> { 0x3a };
            bytes.SetByteAscii(addr);
            bytes.SetByteAscii(func);

            foreach (char s in data)
                bytes.SetByteAscii((byte)s);

            byte crc = LRC.ComputeCheckSum(bytes.ToArray());
            bytes.SetByteAscii(crc);

            bytes.Add(0x0d);
            bytes.Add(0x0a);
            return bytes.ToArray();
        }

        public static bool PraseDataAscii(byte[] data, bool hasCount, out ModbusData ascii)
        {
            int idx = 0;
            ascii = new ModbusData();
            if (data[idx++] != 0x3a)
                return false;

            ascii.Address = data.GetByteAscii(ref idx);     //(byte)((data[idx++] - '0') * 16 + data[idx++] - '0');
            ascii.Function = data.GetByteAscii(ref idx);    //(byte)((data[idx++] - '0') * 16 + data[idx++] - '0');
            if (hasCount)
            {
                ascii.DataLen = data.GetByteAscii(ref idx); //(byte)((data[idx++] - '0') * 16 + data[idx++] - '0');
                if (data.Length < ascii.DataLen * 2 + idx + 4)    //Data字节长度 + Data之前的字节数 + 校验值字节数
                    return false;
            }
            else
                ascii.DataLen = (data.Length - idx - 3) / 2;

            byte lrc = LRC.ComputeCheckSum(data, idx + ascii.DataLen * 2);
            if (false)
                return false;//校验LRC

            ascii.Data = data.GetByteArrAscii(ref idx, ascii.DataLen); //new byte[ascii.DataLen];
            //Buffer.BlockCopy(data, idx, ascii.Data, 0, ascii.DataLen * 2);

            return true;
        }

        #endregion

        #region TCP

        /// https://www.modbus.cn/27847.html
        /// https://www.modbus.cn/28048.html

        /// <summary>
        /// 获取Modbus数据报文（TCP/UDP方式）
        /// </summary>
        /// <param name="func"></param>
        /// <param name="data"></param>
        /// <param name="bmapTId"></param>
        /// <param name="bmapId"></param>
        /// <returns></returns>
        public static byte[] PackDataTcp(byte func, byte[] data, ushort bmapTId, byte bmapId = 0)
        {
            List<byte> bytes = new List<byte>();
            bytes.SetUshort(bmapTId);           //事务处理标志符
            bytes.SetUshort(0);
            ushort len = (ushort)(data.Length + 2); //单元标识符+功能码+数据长度
            bytes.SetUshort(len);
            bytes.Add(bmapId);
            bytes.Add(func);
            bytes.AddRange(data);
            return bytes.ToArray();
        }

        public static bool ParseDataTcp(byte[] data, bool hasCount, out ModbusData tcp, ushort? Tid = null)
        {
            int idx = 0;
            tcp = new ModbusData();
            tcp.MbapTransId = data.GetUshort(ref idx);

            //校验MbapTransId
            if (Tid != null && Tid != 0)
            {
                if (Tid != tcp.MbapTransId)
                    return false;
            }

            idx += 2;
            ushort len = data.GetUshort(ref idx);   //校验长度
            if (data.Length < idx + len)
                return false;

            tcp.MbapUnitId = data[idx++];
            tcp.Function = data[idx++];

            if (hasCount)
            {
                tcp.DataLen = data[idx++];
                if (data.Length < tcp.DataLen + idx)    //Data字节长度 + Data之前的字节数
                    return false;
            }
            else
                tcp.DataLen = data.Length - idx;

            tcp.Data = new byte[tcp.DataLen];
            Buffer.BlockCopy(data, idx, tcp.Data, 0, tcp.DataLen);
            return true;
        }

        #endregion
    }

    enum FuncCode
    {
        读取线圈状态=1,   //1 取得一组逻辑线圈的当前状态（ON/OFF)
        读取输入状态,     //2 取得一组开关输入的当前状态(ON/OFF)
        读取保持寄存器,   //3 在一个或多个保持寄存器中取得当前的二进制值
        读取输入寄存器,   //4 在一个或多个输入寄存器中取得当前的二进制值
        强置单线圈,       //5 强置一个逻辑线圈的通断状态
        预置单寄存器,     //6 放置一个特定的二进制值到一个单寄存器中
        读取异常状态,     //7 取得8个内部线圈的通断状态
        强置多线圈 =15,   //15 强置一串连续逻辑线圈的通断
        预置多寄存器,     //16 放置一系列特定的二进制值到一系列多寄存器中
        报告从机标识      //17 可使主机判断编址从机的类型及该从机运行指示灯的状态
    }

}
